package edu.hawaii.ics314.ical.model;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Objects;

/**
 * The Property class contains a name value pair, and a parameter list.
 * 
 * @author 
 *
 * @param <T> - The runtime type of this property's value.
 */
public class Property<T> {
	
	protected final String NAME;
	protected final T VALUE;
	protected final Property<?>[] PARAMS;
	
	/**
	 * Constructor that creates a new <code>Property</code>.
	 * 
	 * @param name - the name of the property.
	 * @param value - the value of the property.
	 * @param params - the parameters of the property.
	 */
	public Property(String name, T value, Property<?>...params) {
		NAME = Objects.requireNonNull(name);
		VALUE = Objects.requireNonNull(value);
		PARAMS = Objects.requireNonNull(params);
	}
	
	/**
	 * Returns the name of this property.
	 * 
	 * @return The name of this property.
	 */
	public String getName() {
		return NAME;
	}
	
	/**
	 * Returns the value of this property.
	 * 
	 * @return The value of this property.
	 */
	public T getValue() {
		return VALUE;
	}
	
	/**
	 * Search though a property for a specific parameter.
	 * 
	 * @param paramName - the name of the parameter being searched for.
	 * @return The property name if it exists.
	 */
	public Property<?> getParam(String paramName) {
		for(Property<?> prop : PARAMS) {
			if(prop.getName().equals(paramName)) {
				return prop;
			}
		}
		return null;
	}
	
	/**
	 * Returns an array of all the parameters in the property.
	 * 
	 * @return An array of all the parameters in the property.
	 */
	public Property<?>[] getParams() {
		return Arrays.copyOf(PARAMS, PARAMS.length);
	}
	
	/**
	 * Returns an RFC 5455-compliant textual representation of this property and its parameters.
	 * 
	 * @return An RFC 5455-compliant textual representation of this property and its parameters.
	 */
	@Override
	public String toString() {
		final String CRLF = "\r\n";
		StringBuilder out = new StringBuilder();
		out.append(NAME);
		for(int i = 0; i < PARAMS.length; i++) {
			out.append(';');
			out.append(PARAMS[i].NAME);
			out.append('=');
			out.append(PARAMS[i].VALUE);
		}
		out.append(':');
		out.append(VALUE.toString());
		out.append(CRLF);
		return out.toString();
	}
	
	@Override
	public boolean equals(Object other) {
		if(other instanceof Property<?>) {
			Property<?> otherProp = (Property<?>) other;
			return NAME.equals(otherProp.NAME) && VALUE.equals(otherProp.VALUE) 
					&& Arrays.equals(PARAMS, otherProp.PARAMS);
		} else {
			return false;
		}
	}
	
	/**
	 * Parses a string as a new <code>Property</code>.
	 * 
	 * @param property - the string being parsed.
	 * @return the new <code>Property</code>
	 * @throws ParseException if it cannot parse the string.
	 */
	public static Property<String> parse(String property) throws ParseException {
		Objects.requireNonNull(property);
		// Tokenize the input into name, value, and parameters.
		String[] lhsRhsPair = property.split(":");
		if(lhsRhsPair.length != 2 || lhsRhsPair[0].isEmpty() || lhsRhsPair[1].isEmpty()) {
			throw new IllegalArgumentException("malformed property string: " + property);
		}
		String[] paramTokens = lhsRhsPair[0].split(";");
		// Begin parsing 
		String propName = paramTokens[0];
		String propValue = lhsRhsPair[1];
		Property<?>[] params = new Property<?>[(paramTokens.length > 1 ? paramTokens.length - 1 : 0)];
		for(int i = 0; i < params.length; i++) {
			String[] paramNameValPair = paramTokens[i + 1].split("=");
			if(paramNameValPair.length != 2) {
				throw new ParseException(String.format(
						"malformed parameter string '%s' in property string: %s", paramTokens[i + 1], property), -1);
			}
			params[i] = new Property<String>(paramNameValPair[0], paramNameValPair[1]);
		}
		return new Property<String>(propName, propValue, params);
	}
	
}
